/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include <limits>
#include "Bruinbase.h"
#include "BTreeIndex.h"
#include "SqlEngine.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);
BTreeIndex root;

RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

bool SqlEngine::checkCond(int key, string value, const std::vector<SelCond>& cond)
{
	int    diff = 0;
	// check the conditions on the tuple
	for (unsigned i = 0; i < cond.size(); i++) {
		// compute the difference between the tuple value and the condition value
		switch (cond[i].attr) {
		case 1:
			diff = key - atoi(cond[i].value);
			break;
		case 2:
			diff = strcmp(value.c_str(), cond[i].value);
			break;
		}
		// skip the tuple if any condition is not met
		switch (cond[i].comp) {
		case SelCond::EQ:
			if (diff != 0) return false; break;
		case SelCond::NE:
			if (diff == 0) return false; break;
		case SelCond::GT:
			if (diff <= 0) return false; break;
		case SelCond::LT:
			if (diff >= 0) return false; break;
		case SelCond::GE:
			if (diff < 0) return false; break;
		case SelCond::LE:
			if (diff > 0) return false; break;
		}
	}
	return true;
}

void SqlEngine::outputTuple(int attr, int key, string value)
{
	if (attr == 1)
		fprintf(stdout, "%d\n", key);
	else if (attr == 2)
		fprintf(stdout, "%s\n", value.c_str());
	else if (attr == 3)
		fprintf(stdout, "%d '%s'\n", key, value.c_str());
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
	RecordFile rf;   // RecordFile containing the table
	IndexCursor cid;
	RecordId   rid;  // record cursor for table scanning
	PageFile pf;

	//open index file if exists
	bool index = false;
	if (pf.open(table + ".idx", 'r') == 0)
	{
		index = true;
		pf.close();
	}
	if (index)
	{
		root.open(table, 'r');
	}

	RC     rc;
	int    key;
	string value;
	int    count = 0;

	//open table file
	if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
		fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
		return rc;
	}

	bool hasValue = ((attr == 2) || (attr == 3));
	for (int i=0; i<cond.size(); i++)
		if (cond[i].attr == 2 || cond[i].attr == 3)
			hasValue = true;
	//If there exists an equality condition on key, you should always use the equality condition in looking up the index.
	bool existsEquality = false;
	for (int i=0; i<cond.size(); i++)
		if (cond[i].attr == 1 && cond[i].comp == SelCond::EQ)
			existsEquality = true;
	//Queries which specify a range (like key >= 10 and key < 100) must use the index.
	bool existsRange = false;
	for (int i=0; i<cond.size(); i++)
		if (cond[i].attr == 1 && cond[i].comp != SelCond::NE)
			existsRange = true;

	if (existsEquality && index)
	{
		int eqkey = -1;
		for (int i=0; i<cond.size(); i++)
			if (cond[i].attr == 1 && cond[i].comp == SelCond::EQ)
				eqkey = atoi(cond[i].value);
		if (root.locate(eqkey, cid) == 0)
		{
			if (root.readForward(cid, eqkey, rid) == 0)
			{
				if (hasValue)
					if ((rc = rf.read(rid, key, value)) < 0) {
						fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
						goto exit_select;
					}
				if (checkCond(eqkey, value, cond))
				{
					count++;
					outputTuple(attr, eqkey, value);
				}
			}
		}
	}
	else if (existsRange && index)
	{
		bool hasSmallest = false, hasLargest = false;
		int smallestKey = numeric_limits<int>::min();
		int largestKey = numeric_limits<int>::max();
		vector<RecordId> arid;

		for (int i=0; i<cond.size(); i++)
			if (cond[i].attr == 1)
				if (cond[i].comp == SelCond::GE || cond[i].comp == SelCond::GT)
				{
					hasSmallest = true;
					int t = atoi(cond[i].value);
					if (t > smallestKey)
						smallestKey = t;
				}
				else if (cond[i].comp == SelCond::LE || cond[i].comp == SelCond::LT)
				{
					hasLargest = true;
					int t = atoi(cond[i].value);
					if (t < largestKey)
						largestKey = t;
				}

		int flag = 0, curkey = 0;
		if (hasSmallest)
			flag = root.locate(smallestKey, cid);
		else
			flag = root.first(cid);

		while (flag == 0 && root.readForward(cid, curkey, rid) == 0)
		{
			if (hasLargest && curkey > largestKey)
				break;
			if (hasValue) {
				RecordId trid = rid;
				arid.push_back(trid);
			}
			else if (checkCond(curkey, value, cond))
			{
				count++;
				outputTuple(attr, curkey, value);
			}
		}

		if (arid.size()>0)
			sort(arid.begin(), arid.end());
		for (int i=0; i<arid.size(); i++)
		{
			if (hasValue && (rc = rf.read(arid[i], key, value)) < 0) {
				fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
				goto exit_select;
			}
			if (checkCond(key, value, cond))
			{
				count++;
				outputTuple(attr, key, value);
			}
		}
	}
	else if (!hasValue && index)
	{
		int flag = root.first(cid), curkey = 0;
		while (flag == 0)
		{
			flag = root.readForward(cid, curkey, rid);
			if (flag == 0 && hasValue && (rc = rf.read(rid, key, value)) < 0) {
				fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
				goto exit_select;
			}
			if (flag == 0 && checkCond(curkey, value, cond))
			{
				count++;
				outputTuple(attr, curkey, value);
			}
		}
	}

	if ((!existsRange && !existsEquality && hasValue) || !index)
	{
		rid.pid = rid.sid = 0;
		while (rid < rf.endRid()) {
			if ((rc = rf.read(rid, key, value)) < 0) {
				fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
				goto exit_select;
			}
			if (!checkCond(key, value, cond))
				goto next_tuple;
			count++;
			outputTuple(attr, key, value);
			// move to the next tuple
			next_tuple:
			++rid;
		}
	}

	// print matching tuple count if "select count(*)"
	if (attr == 4) {
		fprintf(stdout, "%d\n", count);
	}
	rc = 0;

	// close the table file and return
	exit_select:
	rf.close();
	if (index)
		root.close();
	return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
	string line;
	ifstream reader(loadfile.c_str());
	if (reader.is_open())
	{
		RecordFile rf;
		rf.open(table+".tbl", 'w');
		if (index)
		{
			if (root.open(table, 'w') == 0);
			else
				cout << "First load this table" << endl;
		}

		while (reader.good())
		{
			getline(reader, line);
			if (line.size()<1)
				continue;
			int key;
			string value;
			parseLoadLine(line, key, value);
			RecordId rid;
			rf.append(key, value, rid);

			if (index)
				root.insert(key, rid);
		}
		if (index)
			root.close();
		reader.close();
		rf.close();
	}
	else
		cout << "Unable to open file" << endl;

	//root.statistic();
	return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;

    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');

    // if there is nothing left, set the value to empty string
    if (c == 0) {
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}
